/*
 *
 *
 *Tutorial :
 *
 * How to make (port) a Cartridge subclass (from a Stella class)
 *      1. Constructor 
 *            Use the copyImage(...) on the passed int array to copy the ROM's image
 *            If the ROM has RAM, it can be randomized by randomizeRAM(...) call 
 *      2. Add a serialVersionUID - this simply has to be an original (random) number that you make up; used for serialization (i.e. saved games)
 *      2. Install and Bank methods
 *            Don't worry about the shift and mask stuff...it already knows
 *            If a page access is not a direct poke/peek, use the addIndirectAccess.
 *            If a page access is peek or poke, use the corresponding addDirect___Access.  If there is an offset, use the one
 *                    that has an offset parameter.
 *
 *
 *
 *         For example, let's say this is the original C++ code for Stella :
 *             uInt16 offset = myCurrentBank * 4096;
 uInt16 shift = system->pageShift();
 uInt16 mask = system->pageMask();
 System::PageAccess access;
 access.device = this;
 for(uInt32 address = 0x1200; address < (0x1FF8U & ~mask); address += (1 << shift))
 {
 access.directPokeBase = 0;
 access.directPeekBase = &myImage[offset + (address & 0x0FFF)];
 system->setPageAccess(address >> shift, access);
 }
 *
 *
 *
 *
 *
 *            Here is the JStella version :
 *                  addDirectPeekAccess(0x1200, 0x1FF8, myImage, 0x0FFF, myCurrentBank*4096);
 *
 *
 *            
 *
 */
package dolmisani.toys.emulators.j2600.cart;

import java.io.Serializable;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import static jstella.core.JSConstants.*;
import jstella.core.*;
import jstella.core.IfcDevice;

/**
 * A cartridge is a device which contains the machine code for a game and
 * handles any bankswitching performed by the cartridge. <p> <ul> Terms: <li>
 * ROM : read-only-memory; used interchangeably with cartridge/game,
 * specifically, the data that a cartridge holds <li> Image : the data that a
 * cartridge holds (not an "image" in the typical sense...) <li> MD5 : message
 * digest 5, a string that is produced by looking at (in this case) an array of
 * bytes, and using mathematical formula to derive a unique sort of "serial
 * number" for the given data. </ul>
 *
 * @author Bradford W. Mott
 */
public abstract class Cartridge implements IfcDevice, Serializable {

    private final static long serialVersionUID = 7979700597113264401L;
 
    protected JSSystem system = null;
    protected JSConsole console = null;
    
    protected boolean bankLockedFlag = false;
    protected String md5Digest = null;
    
    protected int[] cartridgeImage = null;
    
    
    protected Cartridge() {
        
        bankLockedFlag = false;
    }

    protected abstract void setCurrentBank(int bank);

    protected abstract int getCurrentBank();

    protected abstract int bankCount();

    public abstract boolean patch(int address, int value);

    @Override
    public String name() {
        return getName();
    }
    
    @Override
    public void reset() {
        //nothing to do
    }
    
    public String getName() {
        return getClass().getSimpleName();
    }
    
    public int[] getImage() {
        return cartridgeImage;
    }
    
    public void lockBank() {
        bankLockedFlag = true;
    }

    public void unlockBank() {
        bankLockedFlag = false;
    }

    /**
     * Returns the MD5 (message digest 5) associated with this ROM
     *
     * @return the MD5 of this ROM
     */
    public String getMD5Digest() {

        return md5Digest;
    }

    public void setConsole(JSConsole console) {
        this.console = console;
    }

    @Override
    public void systemCyclesReset() {
    }

    protected static void randomizeRAM(int[] memory) {
        
        Random random = new Random();

        for (int i = 0; i < memory.length; i++) {
            memory[i] = random.nextInt() & 0xFF;
        }
    }

    protected final void addIndirectAccess(int startAddress, int endAddress) {

        for (int zAddress = (startAddress & ~PAGE_MASK); zAddress < (endAddress & ~PAGE_MASK); zAddress += PAGE_SIZE) {
            system.setPageAccess(zAddress >> PAGE_SHIFT, PageAccess.createIndirectAccess(this));
        }
    }

    protected final void addDirectPeekAccess(int aStartAddress, int aEndAddress, int[] aMemory, int aBaseAddressMask, int aBaseAddressOffset) {
        for (int zAddress = (aStartAddress & ~PAGE_MASK); zAddress < (aEndAddress & ~PAGE_MASK); zAddress += PAGE_SIZE) {
            system.setPageAccess(zAddress >> PAGE_SHIFT, PageAccess.createDirectPeekAccess(this, aMemory, aBaseAddressOffset + (zAddress & aBaseAddressMask)));
        }
    }

    protected final void addDirectPeekAccess(int aStartAddress, int aEndAddress, int[] aMemory, int aBaseAddressMask) {
        addDirectPeekAccess(aStartAddress, aEndAddress, aMemory, aBaseAddressMask, 0);
    }

    protected final void addDirectPokeAccess(int aStartAddress, int aEndAddress, int[] aMemory, int aBaseAddressMask, int aBaseAddressOffset) {
        for (int zAddress = (aStartAddress & ~PAGE_MASK); zAddress < (aEndAddress & ~PAGE_MASK); zAddress += PAGE_SIZE) {
            system.setPageAccess(zAddress >> PAGE_SHIFT, PageAccess.createDirectPokeAccess(this, aMemory, aBaseAddressOffset + (zAddress & aBaseAddressMask)));
        }
    }

    protected final void addDirectPokeAccess(int aStartAddress, int aEndAddress, int[] aMemory, int aBaseAddressMask) {
        addDirectPokeAccess(aStartAddress, aEndAddress, aMemory, aBaseAddressMask, 0);
    }
 
    public static int[] toIntArray(byte[] bytes) {

        int[] result = new int[bytes.length];

        for (int i = 0; i < result.length; i++) {
            result[i] = ((int) bytes[i]) & 0xFF;
        }
        return result;
    }
    
    public static String calculateMD5Digest(byte[] bytes) {

        String result = null;

        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");

            messageDigest.update(bytes);
            byte[] theDigest = messageDigest.digest();

            BigInteger digestValue = new BigInteger(1, theDigest);
            result = digestValue.toString(16);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }

        return result;
    }
    
    @Override
    public String toString() {
        return String.format("%s: md5Digest='%s'", getName(), md5Digest);
    }
}
